package com.yth.utils;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.util.concurrent.ExecutionError;
import com.google.common.util.concurrent.UncheckedExecutionException;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * @param <K> 数据源配置
 * @param <V> 数据源
 * @author yutianhong
 * @version 1.0
 * @since 2024/11/22 11:48
 */
@Slf4j
public abstract class SimpleDatasourceFactory<K, V> {

    /**
     * datasourceCache主动cleanUp时间周期
     */
    private final long cleanUpTimePeriod;

    /**
     * 上次datasourceCache主动cleanUp的时间戳
     */
    private volatile long lastCleanUpTime;

    private final LoadingCache<K, V> datasourceCache;

    /**
     * @param duration the length of time after an entry is last accessed that it should be
     *                 automatically removed
     * @param unit     the unit that {@code duration} is expressed in
     */
    public SimpleDatasourceFactory(long duration, TimeUnit unit) {
        datasourceCache = CacheBuilder.newBuilder()
                .expireAfterAccess(duration, unit)
                .removalListener((RemovalListener<K, V>) notification -> {
                    destroyDatasource(notification.getValue());
                })
                .build(new CacheLoader<K, V>() {
                    @Override
                    public V load(K key) throws Exception {
                        return createDatasource(key);
                    }
                });
        cleanUpTimePeriod = unit.toMillis(duration);
        lastCleanUpTime = System.currentTimeMillis();
    }

    protected abstract V createDatasource(K config) throws Exception;

    protected abstract void destroyDatasource(V dataSource);

    /**
     * 配置错误会导致这个方法抛出异常
     *
     * @param config 数据源配置
     * @return V
     * @throws Exception 配置错误
     */
    public V getDatasource(K config) throws Exception {
        try {
            return datasourceCache.get(config);
        } catch (ExecutionException | UncheckedExecutionException | ExecutionError e) {
            if (e.getCause() instanceof Exception) {
                throw (Exception) e.getCause();
            } else {
                throw e;
            }
        } finally {
            checkAndCleanUpCache();
        }
    }

    // com.google.common.cache对过期entry的清理是惰性的。
    // 而且只会在根据hash查询entry时顺便检查遍历到的部分entry的过期时间。
    // 所以每次获取数据源的时候把全部过期的数据源都清理一下，避免持有过多的过期的数据源连接
    private void checkAndCleanUpCache() {
        long currentTime = System.currentTimeMillis();
        if (currentTime - lastCleanUpTime > cleanUpTimePeriod) {
            synchronized (this) {
                if (currentTime - lastCleanUpTime > cleanUpTimePeriod) {
                    datasourceCache.cleanUp();  // Clean up expired entries
                    lastCleanUpTime = currentTime;  // Update the last cleanUp time
                }
            }
        }
    }

}
